package axis.dynamic;

import java.util.Arrays;
import java.util.Map.Entry;

import org.apache.axis.AxisFault;
import org.apache.axis.constants.Style;
import org.apache.axis.constants.Use;
import org.apache.axis.deployment.wsdd.WSDDService;
import org.apache.axis.description.JavaServiceDesc;
import org.apache.axis.description.ServiceDesc;
import org.apache.axis.handlers.soap.SOAPService;
import org.apache.axis.providers.BasicProvider;


/**
 * Publishes a bean as a web service.
 */
public class SoapReceiver
extends SoapInterface {
  /** the bean that actually implements the service. */
  private Object _target;
  
  /** the service description (name, style, etc.) */
  private final ServiceDesc _service = new JavaServiceDesc();
  
  /** the axis engine configuration. */
  private SoapServerConfiguration _configuration;

  /**
   * @param target bean that actually implements the web service.
   */
  public void setTarget( Object target )
  {
    _target = target;
  }

  /**
   * used as default service name. may be overridden by setting the serviceName property.
   * @param name
   * @see BeanNameAware#setBeanName(String)
   */
  public void setBeanName( String name )
  {
    if (_service.getName() == null) _service.setName(name);
  }

  /**
   * service name. if not set, bean name is used. if that is also not set, {@link #deploy()}
   * will throw an {@link IllegalArgumentException}.
   * @param name
   * @see BeanNameAware#setBeanName(String)
   */
  public void setServiceName( String name )
  {
    _service.setName(name);
  }

  /**
   * Java methods to be published as web service operations. If not set, all methods will be used.
   * I'm not sure if that includes {@link #toString()} etc. See Axis documentation.
   * @param methods Java methods to be published as web service operations.
   */
  public void setAllowedMethods( String[] methods )
  {
    _service.setAllowedMethods(Arrays.asList(methods));
  }
  
  /**
   * if not set, defaults to {@link Style#RPC}.
   * @param style
   * @see SoapInterface#setStyle(Style)
   * @see JavaServiceDesc#setStyle(Style)
   */
  @Override
  public void setStyle( Style style )
  {
    _service.setStyle(style);
  }

  /**
   * if not set, defaults to {@link Use#ENCODED}.
   * @param use
   * @see SoapInterface#setUse(Use)
   * @see JavaServiceDesc#setUse(Use)
   */
  @Override
  public void setUse( Use use )
  {
    _service.setUse(use);
  }

  /**
   * @param configuration
   */
  public void setServerConfiguration( SoapServerConfiguration configuration )
  {
    _configuration = configuration;
  }

  /**
   * calls {@link #deploy()}
   * should only be called once for each configured SoapReceiver.
   * @throws AxisFault if service cannot be created
   * @see InitializingBean#afterPropertiesSet()
   */
  public void afterPropertiesSet() throws AxisFault
  {
    deploy();
  }

  /**
   * should only be called once for each configured SoapReceiver.
   * @throws AxisFault if service cannot be created
   */
  public void deploy() throws AxisFault
  {
    if (_service.getName() == null) throw new IllegalStateException("name not set");
    if (_configuration == null) throw new IllegalStateException("configuration not set");
    _configuration.deployService(_service.getName(), createService());
  }

  /**
   * calls {@link #undeploy()}
   * @see DisposableBean#destroy()
   */
  public void destroy()
  {
    undeploy();
  }

  /**
   * 
   */
  public void undeploy()
  {
    if (_service.getName() == null) throw new IllegalStateException("name not set");
    if (_configuration == null) throw new IllegalStateException("registry not set");
    _configuration.undeployService(_service.getName());
  }
  
  /**
   * @return a new soap service. 
   * @throws AxisFault if service description cannot be initialized
   * @see WSDDService#makeNewInstance
   */
  public SOAPService createService() throws AxisFault
  {
    if (_service.getName() == null) throw new IllegalStateException("name not set");
    if (_target == null) throw new IllegalStateException("target not set");
    
    // note: request and/or response chain may be null
    SOAPService service = new SOAPService(getRequestChain(), new ObjectRPCProvider(_target), getResponseChain());
    service.setName(_service.getName());
    
    // set the wsdl port type to the service name. otherwise, generated wsdl uses the class name for the port type, 
    // which is ugly for Cglib classes. I'm not sure though that this is the correct solution...
    // see org.apache.axis.providers.BasicProvider.generateWSDL() and org.apache.axis.wsdl.fromJava.Emitter.init()
    service.setOption(BasicProvider.OPTION_WSDL_PORTTYPE, _service.getName());
    
    service.setServiceDescription(_service);
    setProperties(service);
    
    // If we don't do this now, something weird will happen: if the service's ?wsdl is generated *before*
    // one of it's operations is invoked, the service won't have *any* operation at all. As long as it lives.
    // But doing this now also may help to catch errors early and speed up the first invocation.
    // Note: the MessageContext parameter can be null - this calls our ObjectRPCProvider in the end, 
    // which ignores the parameter. The usual JavaProvider could handle null as well.
    service.getInitializedServiceDesc(null);
    
    return service;
  }

  /**
   * sets our properties into the given service.
   * @param service
   */
  private void setProperties( SOAPService service )
  {
    for (Entry<String, Object> entry : _properties.entrySet())
    {
      service.setOption(entry.getKey(), entry.getValue());
    }
  }

}

